#include "LightShader.h"
#include "ReportError.h"
#include "Vertex.h"

#include "Graphics.h"

#include <fstream>

LightShader::LightShader()
{
	m_Effect = 0;
	m_Technique = 0;
	m_Layout = 0;

	m_WorldMatrix = 0;
	m_ViewMatrix = 0;
	m_ProjectionMatrix = 0;

	m_LightDirection = 0;
	m_DiffuseColor = 0;
}

LightShader::LightShader(const LightShader& other)
{
}

LightShader::~LightShader()
{
}

bool LightShader::Initialize()
{
	bool result;

	// Initialize the shader that will be used to draw the triangle.
	result = InitializeShader("light.fx");
	if(!result)
	{
		return false;
	}

	return true;
}

void LightShader::Shutdown()
{
	// Shutdown the shader effect.
	ShutdownShader();

	return;
}

void LightShader::Render(int indexCount, Matrix4x4 worldMatrix, Matrix4x4 viewMatrix, Matrix4x4 projectionMatrix, Texture* texture, Vec3 lightDirection, Colour diffuseColor)
{
	// Set the shader parameters that it will use for rendering.
	SetShaderParameters(worldMatrix, viewMatrix, projectionMatrix, texture, lightDirection, diffuseColor);

	// Now render the prepared buffers with the shader.
	RenderShader(indexCount);

	return;
}

bool LightShader::InitializeShader(const std::string& filename)
{
	
	ID3D10Blob* errorMessage;
	// Initialize the error message.
	errorMessage = 0;

	HRESULT result;
	// Load the shader in from the file.
	result = D3DX10CreateEffectFromFile(filename.c_str(), NULL, NULL, "fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, 
		TheGraphics::Instance()->GetD3DObj()->GetDevice(), NULL, NULL, &m_Effect, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if(errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, filename);
		}
		// If there was  nothing in the error message then it simply could not find the shader file itself.
		else
		{
			ReportError("Missing Shader File" + filename);
		}

		return false;
	}

	// Get a pointer to the technique inside the shader.
	m_Technique = m_Effect->GetTechniqueByName("LightTechnique");
	if(!m_Technique)
	{
		return false;
	}

	unsigned int numElements;
	// Get a count of the elements in the layout.
	numElements = sizeof(VertexPNTElements) / sizeof(VertexPNTElements[0]);

	D3D10_PASS_DESC passDesc;
	// Get the description of the first pass described in the shader technique.
	m_Technique->GetPassByIndex(0)->GetDesc(&passDesc);

	// Create the input layout.
	result = TheGraphics::Instance()->GetD3DDevice()->CreateInputLayout(
						VertexPNTElements, 
						numElements, 
						passDesc.pIAInputSignature, 
						passDesc.IAInputSignatureSize, 
					   &m_Layout);
	if(FAILED(result))
	{
		return false;
	}

	// Get pointers to the three matrices inside the shader so we can update them from this class.
	m_WorldMatrix = m_Effect->GetVariableByName("worldMatrix")->AsMatrix();
	m_ViewMatrix = m_Effect->GetVariableByName("viewMatrix")->AsMatrix();
	m_ProjectionMatrix = m_Effect->GetVariableByName("projectionMatrix")->AsMatrix();

	// Get pointer to the texture resource inside the shader
	m_Texture = m_Effect->GetVariableByName("shaderTexture")->AsShaderResource();

	// Set the direction of the light inside the shader.
	m_LightDirection = m_Effect->GetVariableByName("lightDirection")->AsVector();

	// Set the diffuse color of the light inside the shader.
	m_DiffuseColor= m_Effect->GetVariableByName("diffuseColor")->AsVector();


	return true;
}

void LightShader::ShutdownShader()
{
	// Release the pointers to the matrices inside the shader.
	m_WorldMatrix = 0;
	m_ViewMatrix = 0;
	m_ProjectionMatrix = 0;
	m_Texture = 0;

	// Release the pointer to the shader layout.
	if(m_Layout)
	{
		m_Layout->Release();
		m_Layout = 0;
	}

	// Release the pointer to the shader technique.
	m_Technique = 0;

	// Release the pointer to the shader.
	if(m_Effect)
	{
		m_Effect->Release();
		m_Effect = 0;
	}

	return;
}

void LightShader::OutputShaderErrorMessage(ID3D10Blob* errorMessage, const std::string& shaderFilename)
{
	
	char* compileErrors;
	// Get a pointer to the error message text buffer.
	compileErrors = (char*)(errorMessage->GetBufferPointer());

	unsigned long bufferSize;
	// Get the length of the message.
	bufferSize = errorMessage->GetBufferSize();

	std::ofstream fout;
	// Open a file to write the error message to.
	fout.open("shader-error.txt");

	// Write out the error message.
	for(unsigned long i = 0; i < bufferSize; i++)
	{
		fout << compileErrors[i];
	}

	// Close the file.
	fout.close();

	// Release the error message.
	errorMessage->Release();
	errorMessage = 0;

	// Pop a message up on the screen to notify the user to check the text file for compile errors.
	ReportError("Error compiling shader.  Check shader-error.txt for message." + shaderFilename);

	return;
}

void LightShader::SetShaderParameters(Matrix4x4 worldMatrix, Matrix4x4 viewMatrix, Matrix4x4 projectionMatrix,  Texture* texture, Vec3 lightDirection, Colour diffuseColor)
{
	// Set the world matrix variable inside the shader
	m_WorldMatrix->SetMatrix((float*)&worldMatrix);

	// Set the view matrix variable inside the shader
	m_ViewMatrix->SetMatrix((float*)&viewMatrix);

	// Set the projection matrix variable inside the shader
	m_ProjectionMatrix->SetMatrix((float*)&projectionMatrix);

	// Bind the texture
	m_Texture->SetResource(texture->Get());

	// Set the direction of the light inside the shader.
	m_LightDirection->SetFloatVector((float*)&lightDirection);

	// Set the diffuse color of the light inside the shader.
	m_DiffuseColor->SetFloatVector((float*)&diffuseColor);

	return;
}

void LightShader::RenderShader(int indexCount)
{
	// Set the input layout.
	TheGraphics::Instance()->GetD3DDevice()->IASetInputLayout(m_Layout);

	D3D10_TECHNIQUE_DESC techniqueDesc;
	// Get the description structure of the technique from inside the shader so it can be used for rendering.
	m_Technique->GetDesc(&techniqueDesc);

	// Go through each pass in the technique (should be just one currently) and render the triangles.
	for(unsigned int i = 0; i < techniqueDesc.Passes; ++i)
	{
		m_Technique->GetPassByIndex(i)->Apply(0);
		TheGraphics::Instance()->GetD3DDevice()->DrawIndexed(indexCount, 0, 0);
	}

	return;
}